using System;
using System.Windows;
using System.Windows.Media.Animation;
using SStuff.FlowControls;

namespace SStuff.LayoutAlgorithms
{
    public class Circular2DLayout : DependencyObject, IFlowLayout2DAlgorithm
    {

        public Circular2DLayout()
        {
            FadeInEasing = new CubicEase { EasingMode = EasingMode.EaseIn };
            FadeOutEasing = new CubicEase { EasingMode = EasingMode.EaseIn };
        }

        public int MaximumItemCount
        {
            get { return (int)GetValue(MaximumItemCountProperty); }
            set { SetValue(MaximumItemCountProperty, value); }
        }

        // Using a DependencyProperty as the backing store for MaximumItemCount.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty MaximumItemCountProperty =
            DependencyProperty.Register("MaximumItemCount", typeof(int), typeof(Circular2DLayout), new PropertyMetadata(15,
                (s,a)=>
                    {
                        ((Circular2DLayout) s).InvalidateLayout();
                    }));



        public double StartAngle
        {
            get { return (double)GetValue(StartAngleProperty); }
            set { SetValue(StartAngleProperty, value); }
        }

        // Using a DependencyProperty as the backing store for StartAngle.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty StartAngleProperty =
            DependencyProperty.Register("StartAngle", typeof(double), typeof(Circular2DLayout), new PropertyMetadata(-180.0,
                (s,a)=>
                    {
                        var layout = (Circular2DLayout) s;
                        layout._startAngleInRad = layout.StartAngle*Math.PI/180.0;
                        layout.InvalidateLayout();
                    }));



        public double EndAngle
        {
            get { return (double)GetValue(EndAngleProperty); }
            set { SetValue(EndAngleProperty, value); }
        }

        // Using a DependencyProperty as the backing store for EndAngle.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty EndAngleProperty =
            DependencyProperty.Register("EndAngle", typeof(double), typeof(Circular2DLayout), new PropertyMetadata(180.0,
                (s, a) =>
                {
                    var layout = (Circular2DLayout)s;
                    layout._endAngleInRad = layout.EndAngle * Math.PI / 180.0;
                    layout.InvalidateLayout();
                }));




        public double FadeInAngle
        {
            get { return (double)GetValue(FadeInAngleProperty); }
            set { SetValue(FadeInAngleProperty, value); }
        }

        // Using a DependencyProperty as the backing store for FadeInAngle.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty FadeInAngleProperty =
            DependencyProperty.Register("FadeInAngle", typeof(double), typeof(Circular2DLayout), new PropertyMetadata(30.0,
                (s, a) =>
                {
                    var layout = (Circular2DLayout)s;
                    layout._fadeInAngleInRad = layout.FadeInAngle * Math.PI / 180.0;
                    layout.InvalidateLayout();
                }));



        public double FadeOutAngle
        {
            get { return (double)GetValue(FadeOutAngleProperty); }
            set { SetValue(FadeOutAngleProperty, value); }
        }

        // Using a DependencyProperty as the backing store for FadeOutAngle.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty FadeOutAngleProperty =
            DependencyProperty.Register("FadeOutAngle", typeof(double), typeof(Circular2DLayout), new PropertyMetadata(30.0,
                (s, a) =>
                {
                    var layout = (Circular2DLayout)s;
                    layout._fadeOutAngleInRad = layout.FadeOutAngle * Math.PI / 180.0;
                    layout.InvalidateLayout();
                }));




        public EasingFunctionBase FadeInEasing
        {
            get { return (EasingFunctionBase)GetValue(FadeInEasingProperty); }
            set { SetValue(FadeInEasingProperty, value); }
        }

        // Using a DependencyProperty as the backing store for FadeInEasing.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty FadeInEasingProperty =
            DependencyProperty.Register("FadeInEasing", typeof(EasingFunctionBase), typeof(Circular2DLayout), new PropertyMetadata(null,
                (s,a)=>
                {
                    var layout = (Circular2DLayout)s;
                    layout.InvalidateLayout();    
                    }));



        public EasingFunctionBase FadeOutEasing
        {
            get { return (EasingFunctionBase)GetValue(FadeOutEasingProperty); }
            set { SetValue(FadeOutEasingProperty, value); }
        }

        // Using a DependencyProperty as the backing store for FadeOutEasing.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty FadeOutEasingProperty =
            DependencyProperty.Register("FadeOutEasing", typeof(EasingFunctionBase), typeof(Circular2DLayout), new PropertyMetadata(null,
                (s, a) =>
                {
                    var layout = (Circular2DLayout)s;
                    layout.InvalidateLayout();
                }));




        public double VerticalScale
        {
            get { return (double)GetValue(VerticalScaleProperty); }
            set { SetValue(VerticalScaleProperty, value); }
        }

        // Using a DependencyProperty as the backing store for VerticalScale.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty VerticalScaleProperty =
            DependencyProperty.Register("VerticalScale", typeof(double), typeof(Circular2DLayout), new PropertyMetadata(1.0,
                (s,a)=>
                {
                    var layout = (Circular2DLayout)s;
                    layout.InvalidateLayout();
                        
                    }));



        public double CenterX
        {
            get { return (double)GetValue(CenterXProperty); }
            set { SetValue(CenterXProperty, value); }
        }

        // Using a DependencyProperty as the backing store for CenterX.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty CenterXProperty =
            DependencyProperty.Register("CenterX", typeof(double), typeof(Circular2DLayout), new PropertyMetadata(0.5,
                (s, a) =>
                {
                    var layout = (Circular2DLayout)s;
                    layout.InvalidateLayout();

                }));



        public double CenterY
        {
            get { return (double)GetValue(CenterYProperty); }
            set { SetValue(CenterYProperty, value); }
        }

        // Using a DependencyProperty as the backing store for CenterY.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty CenterYProperty =
            DependencyProperty.Register("CenterY", typeof(double), typeof(Circular2DLayout), new PropertyMetadata(0.5,
                (s, a) =>
                {
                    var layout = (Circular2DLayout)s;
                    layout.InvalidateLayout();

                }));





        public double HorizontalScale
        {
            get { return (double)GetValue(HorizontalScaleProperty); }
            set { SetValue(HorizontalScaleProperty, value); }
        }

        // Using a DependencyProperty as the backing store for HorizontalScale.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty HorizontalScaleProperty =
            DependencyProperty.Register("HorizontalScale", typeof(double), typeof(Circular2DLayout), new PropertyMetadata(1.0,
                (s, a) =>
                {
                    var layout = (Circular2DLayout)s;
                    layout.InvalidateLayout();

                }));






        private void InvalidateLayout()
        {
            if (LayoutChanged != null)
                LayoutChanged(this, EventArgs.Empty);
        }


        public static double GetItemRotation(DependencyObject obj)
        {
            return (double)obj.GetValue(ItemRotationProperty);
        }

        public static void SetItemRotation(DependencyObject obj, double value)
        {
            obj.SetValue(ItemRotationProperty, value);
        }

        // Using a DependencyProperty as the backing store for ItemRotation.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty ItemRotationProperty =
            DependencyProperty.RegisterAttached("ItemRotation", typeof(double), typeof(Circular2DLayout), new PropertyMetadata(0.0));

        


        private Size _viewportSize;

        public void SetAvailableSize(Size availableSize)
        {
            _viewportSize = availableSize;
        }

        public int GetMaxDisplayedItems()
        {
            return MaximumItemCount;
        }

        private double _startAngleInRad = -Math.PI;
        private double _endAngleInRad = Math.PI;
        private double _fadeInAngleInRad = 30.0 * Math.PI / 180.0;
        private double _fadeOutAngleInRad = 30.0 * Math.PI / 180.0;

        public void ComputeLayout(double normalizedOffset, IFlowItem2D item, int consideredItemsCount)
        {

            double halfStep = (_endAngleInRad - _startAngleInRad)*.5/consideredItemsCount;
            double range = (_endAngleInRad - _startAngleInRad) - 2.0 * halfStep;

            double normalized01 = normalizedOffset*.5 + .5;

            double itemAngle = range * normalized01 + _startAngleInRad + halfStep;
            item.Transform2D.CenterX = 0.5;
            item.Transform2D.CenterY = 0.5;

            double offsetX = (CenterX - 0.5)*_viewportSize.Width;
            double offsetY = (CenterY - 0.5)*_viewportSize.Height;

            item.Transform2D.TranslateX = Math.Cos(itemAngle) * 0.333 * _viewportSize.Width * HorizontalScale+offsetX;
            item.Transform2D.TranslateY = Math.Sin(itemAngle) * 0.333 * _viewportSize.Height *VerticalScale+offsetY;
            var asDep = item as DependencyObject;
            SetItemRotation(asDep, itemAngle * 180.0 / Math.PI); // todo : scale
            item.RelativeZIndex = -Math.Abs(normalizedOffset);

            item.Opacity = Math.Min(EaseFadeIn(Clamp(Unlerp(_startAngleInRad + halfStep, _startAngleInRad + halfStep + _fadeInAngleInRad, itemAngle))),
                EaseFadeOut(Clamp(Unlerp(_endAngleInRad-halfStep, _endAngleInRad-halfStep-_fadeOutAngleInRad,itemAngle))));
            
            
        }

        private double EaseFadeOut(double value)
        {
            if (FadeOutEasing != null)
                return FadeOutEasing.Ease(value);
            return value;
        }

        double EaseFadeIn(double value)
        {
            if (FadeInEasing != null)
                return FadeInEasing.Ease(value);
            return value;
        }

        private double Lerp(double a, double b, double  t)
        {
            return a + t*(b - a);
        }
        private double Unlerp(double a,double b, double value)
        {
           return (value - a)/(b - a);
        }

        private double Clamp(double value)
        {
            if (value < 0)
                return 0;
            if (value > 1)
                return 1;
            return value;
        }
        

        public bool SupportsGestures
        {
            get { return true; }
        }

        public double GetNormalizedOffsetAtPoint(Point contactPosition)
        {
            double dx = (contactPosition.X - _viewportSize.Width * CenterX) / (HorizontalScale* (_viewportSize.Width/_viewportSize.Height));
            double dy = (contactPosition.Y - _viewportSize.Height * CenterY) / VerticalScale;

            var length = Math.Sqrt(Math.Pow(dx, 2.0) + Math.Pow(dy, 2.0));
            if (length == 0)
                return 0;
            dx /= length;
            dy /= length;
            double angle = Math.Atan2(dy, dx);// todo : scale

            return Unlerp(_startAngleInRad, _endAngleInRad, angle)*2.0 - 1.0;
        }

        public event EventHandler LayoutChanged;
    }
}